﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Globalization;

namespace System.Windows.Art
{
    public struct LabColor : IColor
    {
        private Double scAlpha, scLightness, scA, scB;

        private LabColor(Double scAlpha, Double scLightness, Double scA, Double scB)
        {
            this.scAlpha = scAlpha;
            this.scLightness = scLightness;
            this.scA = scA;
            this.scB = scB;
        }

        public static LabColor FromScLab(Double scAlpha, Double scLightness, Double scA, Double scB)
        {
            return new LabColor(scAlpha, scLightness, scA, scB);
        }

        public ColorSpaceBase Space { get { return ColorSpaces.Lab; } }
        public Color ToColor() { return ColorSpaceConverter.ToColor(this); }

        public Double GetValue(ColorChannel channel)
        {
            var space = ColorSpaces.Lab;
            if (channel == space.Alpha) return this.scAlpha;
            else if (channel == space.Lightness) return this.scLightness;
            else if (channel == space.a) return this.scA;
            else if (channel == space.b) return this.scB;
            else throw new InvalidOperationException();
        }

        public void SetValue(ColorChannel channel, Double value)
        {
            var space = ColorSpaces.Lab;
            if (channel == space.Alpha) this.scAlpha = value;
            else if (channel == space.Lightness) this.scLightness = value;
            else if (channel == space.a) this.scA = value;
            else if (channel == space.b) this.scB = value;
            else throw new InvalidOperationException();
        }

        public Double GetClassicValue(ColorChannel channel)
        {
            var space = ColorSpaces.Lab;
            if (channel == space.Alpha) return this.Alpha;
            else if (channel == space.Lightness) return this.Lightness;
            else if (channel == space.a) return this.A;
            else if (channel == space.b) return this.B;
            else throw new InvalidOperationException();
        }

        public void SetClassicValue(ColorChannel channel, Double value)
        {
            var space = ColorSpaces.Lab;
            if (channel == space.Alpha) this.Alpha = (Byte)value;
            else if (channel == space.Lightness) this.Lightness = (Int32)value;
            else if (channel == space.a) this.A = (SByte)value;
            else if (channel == space.b) this.B = (SByte)value;
            else throw new InvalidOperationException();
        }

        public Byte Alpha
        {
            get
            {
                if (this.scAlpha < 0) return 0;
                else if (this.scAlpha > 1) return 255;
                else return (Byte)Math.Round(255 * this.scAlpha);
            }
            set { this.scAlpha = value / 255.0; }
        }

        /// <summary>
        /// Classic Alpha scaled to [0; 1].
        /// <remarks>Linear, unbounded.</remarks>
        /// </summary>
        public Double ScAlpha
        {
            get { return this.scAlpha; }
            set { this.scAlpha = value; }
        }

        /// <summary>
        /// Must fall into the range [0; 100].
        /// </summary>
        public Int32 Lightness
        {
            get 
            {
                if (this.scLightness < 0) return 0;
                else if (this.scLightness > 1) return 100;
                else return (Int32)Math.Round(100 * this.scLightness); 
            }
            set
            {
                if (value < 0 || value > 100) throw new ArgumentOutOfRangeException("value");
                this.scLightness = value / 100.0;
            }
        }

        /// <summary>
        /// Classic Lightness scaled to [0; 1].
        /// <remarks>Linear, unbounded.</remarks>
        /// </summary>
        public Double ScLightness
        {
            get { return this.scLightness; }
            set { this.scLightness = value; }
        }

        [CLSCompliant(false)]
        public SByte A
        {
            get 
            {
                if (this.scA < -0.5) return -128;
                else if (this.scA > 0.5) return 127;
                else return (SByte)Math.Round(255 * this.scA);
            }
            set { this.scA = value / 255.0; }
        }

        /// <summary>
        /// Classic A scaled to [-0.5; 0.5].
        /// <remarks>Linear, unbounded.</remarks>
        /// </summary>
        public Double ScA
        {
            get { return this.scA; }
            set { this.scA = value; }
        }

        [CLSCompliant(false)]
        public SByte B
        {
            get
            {
                if (this.scB < -0.5) return -128;
                else if (this.scB > 0.5) return 127;
                else return (SByte)Math.Round(255 * this.scB);
            }
            set { this.scB = value / 255.0; }
        }

        /// <summary>
        /// Classic B scaled to [-0.5; 0.5].
        /// <remarks>Linear, unbounded.</remarks>
        /// </summary>
        public Double ScB
        {
            get { return this.scB; }
            set { this.scB = value; }
        }

        public override String ToString()
        {
            return this.ToString("g", CultureInfo.CurrentCulture);
        }

        public String ToString(String format)
        {
            return this.ToString(format, CultureInfo.CurrentCulture);
        }

        public String ToString(String format, IFormatProvider provider)
        {
            var builder = new StringBuilder();

            builder.Append(this.Space.ToString()).Append('#');
            builder.Append(this.scAlpha.ToString(format, provider)).Append("; ");
            builder.Append(this.scLightness.ToString(format, provider)).Append("; ");
            builder.Append(this.scA.ToString(format, provider)).Append("; ");
            builder.Append(this.scB.ToString(format, provider));

            return builder.ToString();
        }

        public Object Clone()
        {
            return this.MemberwiseClone();
        }
    }
}
